﻿using InternetSpiderLib.Scenes;
using InternetSpiderLib.Scenes.UrlQueues;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace InternetSpiderLib
{
    /// <summary>
    /// 网络蜘蛛
    /// </summary>
    public class NetSpider
    {
        /// <summary>
        /// 场景对象
        /// </summary>
        public SceneItem[] SceneItems { get; set; }

        /// <summary>
        /// Url处理队列
        /// </summary>
        public IUrlQueue UrlQueue { get; set; }

        /// <summary>
        /// 网址锁
        /// </summary>
        public IUrlLock UrlLock { get; set; }

        /// <summary>
        /// 初始化的Url
        /// </summary>
        public string[] InitUrls { get; set; }

        public void Run()
        {
            ActionBlock<string> actionBlock = new ActionBlock<string>(url =>
            {
                try
                {
                    HandleUrl(url);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.WriteLine(ex.StackTrace);
                }
            }, new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = 1,
            });

            foreach (var item in InitUrls ?? new string[0])
            {
                UrlQueue?.AddUrl(item, DateTime.Now);
            }

            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    try
                    {
                        while (actionBlock.InputCount >= 10)
                            Thread.Sleep(1000);

                        var list = UrlQueue?.GetQueueItem();
                        if (list == null)
                        {
                            Thread.Sleep(1000);
                            continue;
                        }

                        bool haveNew = false;
                        foreach (var item in list)
                        {
                            if (UrlLock?.IsLockedUrl(item.Key) ?? false)
                                continue;

                            actionBlock.Post(item.Key);
                            haveNew = true;
                        }

                        if (haveNew == false)
                        {
                            Thread.Sleep(1000);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        Console.WriteLine(ex.StackTrace);
                        Thread.Sleep(1000);
                    }
                }
            });
        }

        protected void HandleUrl(string url)
        {
            Console.WriteLine($"Start {url}");

            if (UrlLock != null)
            {
                if (UrlLock.LockUrl(url) == false)
                {
                    Console.WriteLine($"lock {url} faile");
                    Thread.Sleep(1000);
                    return;
                }
            }

            try
            {
                Console.WriteLine($"Try {url}");
                if (UrlQueue.GetUrl(url, out DateTime runTime))
                {
                    if (runTime < DateTime.Now)
                    {
                        UrlQueue.RemoveUrl(url);

                        DataBox response = HttpRequest(url);
                        foreach (var item in SceneItems ?? new SceneItem[0])
                        {
                            item.Run(response);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception {url}");
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
            finally
            {
                Console.WriteLine($"unlock {url}");
                if (UrlLock != null)
                {
                    UrlLock.UnLockUrl(url);
                }
            }
        }

        private DataBox HttpRequest(string url)
        {
            DataBox databox = new DataBox();

            WebRequest request = WebRequest.Create(url);
            request.Credentials = CredentialCache.DefaultCredentials;
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            databox.Set<int>(SceneContextKey.StatusCode, (int)response.StatusCode);
            databox.Set<string>(SceneContextKey.Url, url);

            using (Stream dataStream = response.GetResponseStream())
            {
                using (StreamReader reader = new StreamReader(dataStream))
                {
                    databox.Set<string>(SceneContextKey.HtmlBody, reader.ReadToEnd());
                }
            }
            response.Close();

            return databox;
        }
    }
}
